//你好
const { def, base, intField, setEvent, regFieldGetSet, regEvent } = require("./jsdemo_windows.common.cpp.js");

const file = String.raw/* cpp */`

  typedef struct {
    FIL fil;
    JSValue js_obj;
  } Stream;

  static void js_stream_finalizer(JSRuntime *rt, JSValue val) {
    printf("js_stream_finalizer\n");
    Stream *p = (Stream*)JS_GetOpaque(val);
    if(p){
      f_close(&p->fil);
      free(p);
    }
  }

  void* getBuffer(JSValueConst arg, size_t* size){
    JSClassID cid = JS_GetClassID(arg); 
    if(cid==JS_CLASS_ARRAY_BUFFER){
      void* buff = JS_GetArrayBuffer(ctx, size, arg);
      return buff;
    }else if(cid>=JS_CLASS_UINT8C_ARRAY && cid<=JS_CLASS_FLOAT32_ARRAY){
      JSValue buffer = JS_GetPropertyStr(ctx, arg, "buffer");
      int tag = JS_VALUE_GET_TAG(buffer);
      if(tag != JS_TAG_UNDEFINED){
        void* buff = JS_GetArrayBuffer(ctx, size, buffer);
        return buff;
      }else{
        return 0;
      }
    }else{
      return 0;
    }
  }

  static JSValue js_stream_read(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    FRESULT res;
    unsigned int byteRead;
    Stream *stream = (Stream*)JS_GetOpaque(this_val);
    if(argc==1 || argc==2){
      size_t size;
      void* buff = getBuffer(argv[0], &size);

      int size2;
      if(argc==2){
        JS_ToInt32(ctx, &size2, argv[1]);
        if(size2 && size2 < size){
          size = size2;
        }
      }

      res = f_read(&stream->fil, buff, size, &byteRead);
      if (res == FR_OK) {
        return JS_NewInt32(ctx, byteRead);
      }
    }else{
      uint8_t buff;

      res = f_read(&stream->fil, &buff, 1, &byteRead);
      if (res == FR_OK && byteRead == 1) {
        return JS_NewInt32(ctx, buff);
      }
    }

    return JS_UNDEFINED;
  }

  static JSValue js_stream_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    Stream *stream = (Stream*)JS_GetOpaque(this_val);
    //stream->fil;
    return JS_UNDEFINED;
  }

  static JSValue js_stream_seek(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    FRESULT res;
    Stream *stream = (Stream*)JS_GetOpaque(this_val);

    if(argc==1){
      int pos;
      JS_ToInt32(ctx, &pos, argv[0]);
      res = f_lseek(&stream->fil, pos);
      if (res == FR_OK) {
        return JS_NewInt32(ctx, pos);
      }
    }

    return JS_UNDEFINED;
  }

  static JSValue js_fs_open(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    FRESULT res;

    const char *str = JS_ToCString(ctx, argv[0]);
    int write = 0;
    JS_ToInt32(ctx, &write, argv[1]);
    
    Stream* p = (Stream*)malloc(sizeof(Stream));
    res = f_open(&p->fil, str, FA_READ);//TODO write
    JS_FreeCString(ctx, str);
    if (res == FR_OK) {
      JSValue obj = JS_NewObjectClass(ctx, js_stream_class_id);
      JS_SetOpaque(obj, p);
      p->js_obj = obj;
      return obj;
    }
    free(p);
    return JS_UNDEFINED;
  }

  static JSValue js_fs_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    FRESULT res;
    FILINFO fno;
    const char *str = JS_ToCString(ctx, argv[0]);
    res = f_stat(str, &fno);
    JS_FreeCString(ctx, str);
    if (res == FR_OK) {
      JSValue obj = JS_NewObject(ctx);
      JS_SetPropertyStr(ctx, obj, "isdir", JS_NewInt32(ctx, (fno.fattrib & AM_DIR) ? 1 : 0));
      JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, fno.fname));
      JS_SetPropertyStr(ctx, obj, "size", JS_NewInt32(ctx, fno.fsize));
      return obj;
    }
    return JS_UNDEFINED;
  }

  static JSValue js_fs_list(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    FRESULT res;
    DIR dir;
    FILINFO fno;
    const char *str = JS_ToCString(ctx, argv[0]);
    res = f_opendir(&dir, str);
    JS_FreeCString(ctx, str);
    if (res == FR_OK) {
      JSValue list = JS_NewArray(ctx);
      int idx = 0;
      for (;;) {
        res = f_readdir(&dir, &fno);
        
        if (fno.fname[0] == 0) break;
        JSValue obj = JS_NewObject(ctx);
        JS_SetPropertyStr(ctx, obj, "isdir", JS_NewInt32(ctx, (fno.fattrib & AM_DIR) ? 1 : 0));
        JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, fno.fname));
        JS_SetPropertyStr(ctx, obj, "size", JS_NewInt32(ctx, fno.fsize));
        JS_SetPropertyUint32(ctx, list, idx, obj);
        idx++;
      }
      f_closedir(&dir);

      return list;

    }
    return JS_UNDEFINED;
  }



  Sd2Card sdcard;
  CH375b ch375b;

  DSTATUS disk_status(BYTE pdrv){
    return RES_OK;
  }


  DSTATUS disk_initialize(BYTE pdrv){
    int res;
    int cardidx = 0;
    res = sdcard.init(cardidx);
    if(res){
      printf("found card %d\r\n", cardidx);
    }else{
      printf("retry\r\n");
      res = sdcard.init(cardidx);
      if(res){
        printf("found card %d\r\n", cardidx);
      }else{
        return STA_NOINIT;
      }
    }
    return RES_OK;
  }


  DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count)  {
    char *p = (char*)buff;
    for(int i=0;i<count;i++){
      sdcard.readBlock(&sdcard, sector+i, p);
      p+=512;
    }
    return RES_OK;
  }


  DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count){
    char *p = (char*)buff;
    for(int i=0;i<count;i++){
      sdcard.writeBlock(&sdcard, sector+i, p);
      p+=512;
    }
    return RES_OK;
  }

  DRESULT disk_ioctl(BYTE pdrv, BYTE cmd,	void *buff){
    int cardidx = 0;
    //   CTRL_SYNC	Makes sure that the device has finished pending write process. If the disk I/O layer or storage device has a write-back cache, the dirty cache data must be committed to the medium immediately. Nothing to do for this command if each write operation to the medium is completed in the disk_write function.
    if(cmd == CTRL_SYNC){
      return RES_OK;
    }
    // GET_SECTOR_COUNT	Retrieves number of available sectors (the largest allowable LBA + 1) on the drive into the LBA_t variable that pointed by buff. This command is used by f_mkfs and f_fdisk function to determine the size of volume/partition to be created.
    if(cmd == GET_SECTOR_COUNT){
      LBA_t *buff_	= (LBA_t*)buff;
      int Capacity = sdcard.getSize(cardidx);
      *buff_ = Capacity/512;// only on create partition
      return RES_OK;
    }
    // GET_SECTOR_SIZE	Retrieves sector size (minimum data unit for generic read/write) into the WORD variable that pointed by buff. Valid sector sizes are 512, 1024, 2048 and 4096. This command is required only if FF_MAX_SS > FF_MIN_SS. When FF_MAX_SS == FF_MIN_SS, this command will never be used and the disk_read and disk_write function must work in FF_MAX_SS bytes/sector.
    if(cmd == GET_SECTOR_SIZE){
      return RES_OK;
    }
    // GET_BLOCK_SIZE	Retrieves erase block size in unit of sector of the flash memory media into the DWORD variable that pointed by buff. The allowable value is 1 to 32768 in power of 2. Return 1 if it is unknown or in non flash memory media. This command is used by f_mkfs function with block size not specified and it attempts to align the data area on the suggested block boundary. Note that FatFs does not have FTL (flash translation layer), so that either disk I/O layter or storage device must have an FTL in it.
    if(cmd == GET_BLOCK_SIZE){
      return RES_OK;
    }
    // CTRL_TRIM	Informs the disk I/O layter or the storage device that the data on the block of sectors is no longer needed and it can be erased. The sector block is specified in an LBA_t array {<Start LBA>, <End LBA>} that pointed by buff. This is an identical command to Trim of ATA device. Nothing to do for this command if this funcion is not supported or not a flash memory device. FatFs does not check the result code and the file function is not affected even if the sector block was not erased well. This command is called on remove a cluster chain and in the f_mkfs function. It is required when FF_USE_TRIM == 1.
    if(cmd == CTRL_TRIM){
      return RES_OK;
    }
    return RES_OK;
  }

  DWORD get_fattime (void){
    return 0;
  }


  FATFS FatFs;

  void file_init() {
    f_mount(&FatFs, "/", 0);

    {
      JSValue fs = JS_NewObject(ctx);
            
      JS_SetPropertyStr(ctx, fs, "list", JS_NewCFunction(ctx, js_fs_list, "list", 1));
      JS_SetPropertyStr(ctx, fs, "info", JS_NewCFunction(ctx, js_fs_info, "info", 1));
      JS_SetPropertyStr(ctx, fs, "open", JS_NewCFunction(ctx, js_fs_open, "open", 2));

      JS_SetPropertyStr(ctx, global, "fs", fs);
    }



    JS_NewClassID(&js_stream_class_id);
    JSClassDef stream_def = {
      .class_name = "Stream",
      .finalizer = js_stream_finalizer,
    };
    JS_NewClass(rt, js_stream_class_id, &stream_def);

    JSValue stream_proto = JS_NewObject(ctx);

    // JS_DefinePropertyGetSet(
    //       ctx, stream_proto,
    //       JS_NewAtom(ctx, "type"),
    //       JS_NewCFunction(ctx, js_stream_get_type, "type", 0),
    //       JS_UNDEFINED,
    //         JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE);

    JS_SetClassProto(ctx, js_stream_class_id, stream_proto);

    JS_SetPropertyStr(ctx, stream_proto, "read", JS_NewCFunction(ctx, js_stream_read, "read", 1));
    JS_SetPropertyStr(ctx, stream_proto, "write", JS_NewCFunction(ctx, js_stream_write, "write", 2));
    JS_SetPropertyStr(ctx, stream_proto, "seek", JS_NewCFunction(ctx, js_stream_seek, "seek", 1));


  }




`;

module.exports = { file };